﻿using System;
using System.IO;
using System.Xml;

namespace Infrastructure.CrossCutting.Configuration
{
    public abstract class XmlFileBasedConfigSource : FileBasedConfigSource
    {
        public XmlFileBasedConfigSource(string name, bool enableWatching)
            : base(name, enableWatching)
        {

        }


        public override void Init()
        {
            XmlDocument configDoc = new XmlDocument();
            Clear();
            FileStream fs = null;
            try
            {
                // open file in a share mode for read only.
                // we to open the file in a non-exclusive mode.
                fs = new FileStream(SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                configDoc.Load(fs);
            }
            finally
            {
                if (fs != null) fs.Close();
            }
            string configSectionXpathExpression = "//ConfigSection";
            XmlNodeList list = configDoc.SelectNodes(configSectionXpathExpression);

            LoadConfigSection(configDoc.DocumentElement, true);

            if (list != null)
            {
                foreach (XmlElement configSectionElement in list)
                {
                    LoadConfigSection(configSectionElement, false);
                }
            }
        }

        public override void ChangeConfigItem(string configItemName, object value)
        {
            XmlDocument configDoc = new XmlDocument();
            FileStream fs = null;
            try
            {
                // open file in a share mode for read only.
                // we to open the file in a non-exclusive mode.
                fs = new FileStream(SourcePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                configDoc.Load(fs);
            }
            finally
            {
                if (fs != null) fs.Close();
            }
            XmlNode node = configDoc.SelectSingleNode("//ConfigItem[@name=\"" + configItemName + "\"]");

            if (node == null)
            {
                throw new ConfigurationException("Couldn't find the configItem " + configItemName + " on the config file " + SourcePath);
            }

            if (node.Attributes != null && node.Attributes["value"] != null)
            {
                node.Attributes["value"].Value = value.ToString();
            }
            else
            {
                throw new ConfigurationException("Couldn't find the configItem " + configItemName + " on the config file " + SourcePath);
            }

            configDoc.Save(SourcePath);
        }

        private void LoadConfigSection(XmlElement configSectionElement, bool isMainConfigSection)
        {
            string sectionName = string.Empty;
            if (!isMainConfigSection)
                sectionName = configSectionElement.GetAttribute("name");
            foreach (XmlElement element in configSectionElement.ChildNodes)
            {
                if (element.Name.Equals("ConfigItem"))
                {
                    string itemName = element.GetAttribute("name");
                    string valueAsString = element.GetAttribute("value");
                    object value = element.HasAttribute("CastTo")
                                       ? CastTo(valueAsString, element.GetAttribute("CastTo"))
                                       : valueAsString;
                    if (isMainConfigSection)
                        AddItem(itemName, value);
                    else
                        AddToSection(sectionName, itemName, value);
                }
            }
        }

        private object CastTo(string valueAsString, string castTo)
        {
            switch (castTo)
            {
                case "Int32":
                    return Convert.ToInt32(valueAsString);
                case "string":
                    return valueAsString;
                case "bool":
                    return Convert.ToBoolean(valueAsString);
                default:
                    throw new ConfigurationException("Cast to " + castTo + " is not implemented.");
            }
        }

        /// <exception cref="ConfigurationException"><c>ConfigurationException</c>.</exception>
        public override void Save()
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                XmlElement rootElement = doc.CreateElement("Configuration");
                doc.AppendChild(rootElement);


                foreach (var key in Keys)
                {
                    if (key is string)
                    {
                        var value = this[key];
                        XmlElement element = null;
                        if (value.GetType() == typeof(string))
                        {
                            element = doc.CreateElement("ConfigItem");
                            element.SetAttribute("name", key.ToString());
                            element.SetAttribute("value", value.ToString());
                        }
                        else if (value.GetType() == typeof(string))
                        {
                            //todo
                        }
                        else if (value.GetType() == typeof(string))
                        {
                            //todo
                        }
                        else if (value.GetType() == typeof(string))
                        {
                            //todo
                        }
                        else
                        {
                            //todo
                        }
                        if (element != null)
                            rootElement.AppendChild(element);
                    }
                }

                doc.Save(configFilePath);
            }
            catch (Exception ex)
            {
                throw new ConfigurationException("Failed to save the configuration file " + configFilePath + ": " + ex.Message, ex);
            }
        }
    }

}
